"""回测评估指标."""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import List

import numpy as np
import pandas as pd

from .config import BacktestConfig
from .walk_forward import BacktestResult

logger = logging.getLogger(__name__)


@dataclass
class BacktestMetrics:
    """回测指标."""

    # 收益指标
    cumulative_return: float  # 累计收益率（等同 total_return，显式字段）
    total_return: float  # 向后兼容
    annualized_return: float  # 年化收益率（252交易日）
    annualized_volatility: float  # 年化波动率
    downside_volatility: float  # 年化下行波动率

    # 风险指标
    sharpe_ratio: float  # 夏普比
    sortino_ratio: float  # 索提诺比
    calmar_ratio: float  # 卡玛比
    max_drawdown: float  # 最大回撤
    win_rate: float  # 胜率

    # 盈亏统计
    avg_gain: float  # 平均盈利
    avg_loss: float  # 平均亏损（取绝对值）
    gain_loss_ratio: float  # 盈亏比

    # 相对收益指标
    information_ratio: float  # 信息比率
    tracking_error: float  # 跟踪误差
    excess_return: float  # 超额收益（策略年化 - 基准年化）

    # 其他指标
    total_trades: int  # 总交易次数
    avg_holding_period: float  # 平均持有期（交易日）
    stop_loss_count: int  # 止损触发次数
    take_profit_count: int  # 止盈触发次数


@dataclass
class ModelMetrics:
    """模型评估指标."""

    auc: float  # AUC
    accuracy: float  # 准确率
    f1: float  # F1 分数
    feature_importance: dict[str, float] | None  # 特征重要性（如适用）


def calculate_backtest_metrics(
    results: List[BacktestResult],
    config: BacktestConfig,
    benchmark_returns: pd.Series | None = None,
) -> BacktestMetrics:
    """计算回测指标.

    Parameters
    ----------
    results:
        回测结果列表（来自多个窗口）
    config:
        回测配置
    benchmark_returns:
        基准收益率序列（用于计算信息比率）

    Returns
    -------
    回测指标
    """
    # 合并所有窗口的净值曲线和收益
    all_equity = []
    all_returns = []
    all_trades = []
    all_positions = []

    for result in results:
        all_equity.append(result.equity_curve)
        all_returns.append(result.daily_returns)
        all_trades.extend(result.trades)
        all_positions.extend(result.positions)

    if not all_equity:
        raise ValueError("回测结果为空")

    # 修复净值曲线合并逻辑：将每个窗口的净值曲线转换为相对收益率序列，然后累乘
    # 问题：每个窗口的净值曲线都从 initial_capital (1.0) 开始，直接合并会导致净值曲线不连续
    # 解决：将每个窗口的净值曲线转换为相对于窗口起始值的倍数，然后按时间顺序累乘
    
    # 按时间顺序排序所有窗口
    sorted_results = sorted(results, key=lambda r: r.equity_curve.index[0] if len(r.equity_curve) > 0 else pd.Timestamp.min)
    
    # 构建连续的净值曲线
    equity_curve_parts = []
    cumulative_multiplier = 1.0  # 累积倍数，用于连接不同窗口
    
    for i, result in enumerate(sorted_results):
        if len(result.equity_curve) == 0:
            continue
            
        window_equity = result.equity_curve.copy()
        window_start_value = window_equity.iloc[0]
        
        # 将窗口的净值曲线转换为相对于起始值的倍数
        # 如果起始值为0，跳过该窗口
        if window_start_value <= 0:
            continue
            
        window_multiplier = window_equity / window_start_value
        
        # 应用累积倍数，使净值曲线连续
        window_equity_normalized = window_multiplier * cumulative_multiplier * config.initial_capital
        
        # 更新累积倍数：使用当前窗口的结束倍数作为下一个窗口的起始倍数
        # 如果窗口有重叠，重叠部分会被后续窗口覆盖（使用 keep="last"）
        if len(window_equity_normalized) > 0:
            cumulative_multiplier = window_equity_normalized.iloc[-1] / config.initial_capital
        
        equity_curve_parts.append(window_equity_normalized)
    
    # 合并所有窗口的净值曲线
    if equity_curve_parts:
        equity_curve = pd.concat(equity_curve_parts).sort_index()
        # 去除重复日期（保留最后一个，处理窗口重叠）
        equity_curve = equity_curve[~equity_curve.index.duplicated(keep="last")]
    else:
        # 如果没有有效的净值曲线，使用原始方法作为后备
        equity_curve = pd.concat(all_equity).sort_index()
        equity_curve = equity_curve[~equity_curve.index.duplicated(keep="last")]
    
    # 合并日收益率序列（去除重复日期）
    daily_returns = pd.concat(all_returns).sort_index()
    daily_returns = daily_returns[~daily_returns.index.duplicated(keep="last")]

    # 计算总收益率
    initial_value = equity_curve.iloc[0]
    final_value = equity_curve.iloc[-1]
    total_return = (final_value - initial_value) / initial_value

    # 计算年化收益率
    n_days = len(equity_curve)
    if n_days > 0:
        annualized_return = (1 + total_return) ** (252 / n_days) - 1
    else:
        annualized_return = 0.0

    # 计算年化波动率
    if len(daily_returns) > 0:
        daily_vol = daily_returns.std(ddof=0)
        annualized_volatility = daily_vol * np.sqrt(252)
        downside_returns = daily_returns[daily_returns < 0]
        if len(downside_returns) > 0:
            downside_volatility = downside_returns.std(ddof=0) * np.sqrt(252)
        else:
            downside_volatility = 0.0
    else:
        annualized_volatility = 0.0
        downside_volatility = 0.0

    sortino_ratio = (
        (annualized_return - config.risk_free_rate) / downside_volatility
        if downside_volatility > 0
        else 0.0
    )

    # 计算夏普比
    if annualized_volatility > 0:
        sharpe_ratio = (annualized_return - config.risk_free_rate) / annualized_volatility
    else:
        sharpe_ratio = 0.0

    # 计算最大回撤
    cummax = equity_curve.cummax()
    drawdown = (equity_curve - cummax) / cummax
    max_drawdown = float(drawdown.min())
    calmar_ratio = (
        annualized_return / abs(max_drawdown) if abs(max_drawdown) > 1e-9 else 0.0
    )

    # 计算胜率（基于交易记录，而非日收益）
    # 统计盈利交易：卖出交易中，total_cost < 0 表示盈利
    # 说明：卖出时 total_cost = 持仓价值 - 实际收益，如果total_cost < 0，说明实际收益 > 持仓价值，即盈利
    sell_trades = [t for t in all_trades if t.action == "sell"]
    if len(sell_trades) > 0:
        # 统计盈利的卖出交易
        # 注意：total_cost在卖出时 = capital - new_capital，如果total_cost < 0，说明new_capital > capital，即盈利
        profitable_trades = sum(1 for t in sell_trades if t.total_cost < 0)
        win_rate = profitable_trades / len(sell_trades)
    else:
        # 如果没有卖出交易，胜率为0
        win_rate = 0.0

    # 计算信息比率、跟踪误差、超额收益
    information_ratio = 0.0
    tracking_error = 0.0
    excess_return = 0.0
    benchmark_annualized_return = 0.0
    if benchmark_returns is not None and len(benchmark_returns) > 0:
        aligned_returns = daily_returns.reindex(benchmark_returns.index, fill_value=0.0)
        benchmark_aligned = benchmark_returns.reindex(aligned_returns.index, fill_value=0.0)
        excess_series = aligned_returns - benchmark_aligned
        if len(excess_series) > 0:
            daily_te = excess_series.std(ddof=0)
            tracking_error = float(daily_te * np.sqrt(252))
            if tracking_error > 0:
                information_ratio = float(excess_series.mean() / daily_te)
                information_ratio *= np.sqrt(252)

        # 计算基准年化收益
        bench_total = (1 + benchmark_aligned).prod() - 1
        if len(benchmark_aligned) > 0:
            benchmark_annualized_return = (1 + bench_total) ** (252 / len(benchmark_aligned)) - 1

        excess_return = annualized_return - benchmark_annualized_return

    # 统计交易信息
    total_trades = len(all_trades)
    stop_loss_count = sum(r.stop_loss_count for r in results)
    take_profit_count = sum(r.take_profit_count for r in results)

    # 计算平均盈利/亏损及盈亏比（基于卖出交易）
    avg_gain = 0.0
    avg_loss = 0.0
    gain_loss_ratio = 0.0
    if sell_trades:
        pnls = [-t.total_cost for t in sell_trades]  # total_cost<0 表示盈利
        gains = [p for p in pnls if p > 0]
        losses = [p for p in pnls if p < 0]
        if gains:
            avg_gain = float(np.mean(gains))
        if losses:
            avg_loss = float(abs(np.mean(losses)))
        if avg_loss > 0:
            gain_loss_ratio = avg_gain / avg_loss

    # 计算平均持有期
    holding_periods = []
    current_entry_date = None
    for pos in sorted(all_positions, key=lambda x: x.date):
        if pos.quantity > 0 and current_entry_date is None:
            current_entry_date = pos.date
        elif pos.quantity == 0 and current_entry_date is not None:
            holding_period = (pos.date - current_entry_date).days
            holding_periods.append(holding_period)
            current_entry_date = None
    avg_holding_period = np.mean(holding_periods) if holding_periods else 0.0

    # 回测指标合理性检查
    # 检查1：总收益率异常高（超过1000%）
    if abs(total_return) > 10.0:  # 总收益率超过1000%
        logger.warning(
            f"回测总收益率异常高: {total_return:.2%}，可能存在计算错误。"
            f"请检查净值曲线计算逻辑或数据泄露问题。"
        )
    
    # 检查2：胜率异常低但收益率异常高（矛盾指标）
    if win_rate < 0.05 and total_return > 0.5:  # 胜率<5%但收益率>50%
        logger.warning(
            f"回测指标矛盾: 胜率={win_rate:.2%}，总收益率={total_return:.2%}。"
            f"低胜率高收益通常不合理，可能存在计算错误。"
        )
    
    # 检查3：净值曲线异常波动（单日变化超过50%）
    if len(equity_curve) > 1:
        daily_changes = equity_curve.pct_change().dropna()
        if (daily_changes.abs() > 0.5).any():  # 单日变化超过50%
            extreme_days = daily_changes[daily_changes.abs() > 0.5]
            logger.warning(
                f"检测到净值曲线异常波动: {len(extreme_days)} 个交易日变化超过50%，"
                f"最大单日变化: {daily_changes.abs().max():.2%}。"
                f"可能存在计算错误或数据问题。"
            )
    
    # 检查4：年化收益率异常高（超过500%）
    if abs(annualized_return) > 5.0:  # 年化收益率超过500%
        logger.warning(
            f"年化收益率异常高: {annualized_return:.2%}，可能存在计算错误。"
            f"请检查回测逻辑和数据质量。"
        )
    
    # 检查5：夏普比率异常高（超过10）
    if sharpe_ratio > 10.0:
        logger.warning(
            f"夏普比率异常高: {sharpe_ratio:.2f}，通常合理的夏普比率在0-3之间。"
            f"请检查收益率和波动率的计算是否正确。"
        )
    
    # 检查6：交易次数异常（相对于数据量）
    if len(equity_curve) > 0:
        trades_per_day = total_trades / len(equity_curve)
        if trades_per_day > 0.5:  # 平均每天交易超过0.5次（即每2天至少1次交易）
            logger.warning(
                f"交易频率异常高: 总交易次数={total_trades}，数据天数={len(equity_curve)}，"
                f"平均每{trades_per_day:.2f}天一次交易。可能存在过度交易问题。"
            )

    return BacktestMetrics(
        cumulative_return=float(total_return),
        total_return=float(total_return),
        annualized_return=float(annualized_return),
        annualized_volatility=float(annualized_volatility),
        downside_volatility=float(downside_volatility),
        sharpe_ratio=float(sharpe_ratio),
        sortino_ratio=float(sortino_ratio),
        calmar_ratio=float(calmar_ratio),
        max_drawdown=float(max_drawdown),
        win_rate=float(win_rate),
        avg_gain=float(avg_gain),
        avg_loss=float(avg_loss),
        gain_loss_ratio=float(gain_loss_ratio),
        information_ratio=float(information_ratio),
        tracking_error=float(tracking_error),
        excess_return=float(excess_return),
        total_trades=total_trades,
        avg_holding_period=float(avg_holding_period),
        stop_loss_count=stop_loss_count,
        take_profit_count=take_profit_count,
    )


def calculate_model_metrics(
    y_true: np.ndarray,
    y_pred_proba: np.ndarray,
    feature_importance: dict[str, float] | None = None,
) -> ModelMetrics:
    """计算模型评估指标.

    Parameters
    ----------
    y_true:
        真实标签
    y_pred_proba:
        预测概率
    feature_importance:
        特征重要性（可选）

    Returns
    -------
    模型指标
    """
    from sklearn.metrics import accuracy_score, f1_score, roc_auc_score

    y_pred = (y_pred_proba >= 0.5).astype(int)

    auc = float(roc_auc_score(y_true, y_pred_proba))
    accuracy = float(accuracy_score(y_true, y_pred))
    f1 = float(f1_score(y_true, y_pred))

    return ModelMetrics(
        auc=auc,
        accuracy=accuracy,
        f1=f1,
        feature_importance=feature_importance,
    )

